package com.dliyun.oap.framework.context;

import com.dliyun.oap.framework.SystemParameterNames;
import com.dliyun.oap.framework.annotation.HttpAction;
import com.dliyun.oap.framework.request.AbstractOapRequest;
import com.dliyun.oap.framework.request.OapConverter;
import com.dliyun.oap.framework.request.OapRequest;
import com.dliyun.oap.framework.service.ServiceMethodHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.ServletRequestDataBinder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/**
 * @author stjdydayou
 */
@Slf4j
public class ServletRequestContextBuilder implements RequestContextBuilder {
    /**
     * 通过前端的负载均衡服务器时，请求对象中的IP会变成负载均衡服务器的IP，因此需要特殊处理，下同。
     */
    private final static String[] REMOTE_IP_ADDRESS = {"x-forwarded-for", "x-real-ip", "proxy-client-ip", "wl-proxy-client-ip"};

    private final FormattingConversionService formattingConversionService;

    private Validator validator;

    public ServletRequestContextBuilder() {
        this.formattingConversionService = this.getDefaultConversionService();
    }

    @Override
    public OapRequestContext buildBySysParams(OapContext oapContext, HttpServletRequest servletRequest, HttpServletResponse servletResponse,
                                              Map<String, Object> requestParams, String appKey) {

        OapRequestContext orc = new OapRequestContext();

        // 设置请求对象及参数列表
        orc.setRequest(servletRequest);
        orc.setResponse(servletResponse);

        String clientIp = this.getClientIp(servletRequest);
        orc.setClientIp(clientIp);

        // 设置服务的系统级参数
        orc.setAppKey(appKey);
        orc.setMethod(requestParams.getOrDefault(SystemParameterNames.METHOD, "").toString());
        orc.setVersion(requestParams.getOrDefault(SystemParameterNames.VERSION, "").toString());
        orc.setHttpAction(HttpAction.fromValue(servletRequest.getMethod()));

        // 设置服务处理器
        ServiceMethodHandler serviceMethodHandler = oapContext.getServiceMethodHandler(orc.getMethod(), orc.getVersion());
        orc.setServiceMethodHandler(serviceMethodHandler);

        return orc;
    }

    @Override
    public OapRequest buildOapRequest(OapRequestContext requestContext, Map<String, Object> requestParams) {
        AbstractOapRequest oapRequest;
        if (requestContext.getServiceMethodHandler().isRequestImplType()) {
            BindingResult bindingResult = doBind(requestParams, requestContext.getServiceMethodHandler().getRequestType());
            oapRequest = buildRequestFromBindingResult(requestContext, bindingResult);

            List<ObjectError> bindingErrors = bindingResult.getAllErrors();
            requestContext.setBindingErrors(bindingErrors);
        } else {
            oapRequest = new AbstractOapRequest() {
            };
        }
        assert oapRequest != null;
        oapRequest.setRequestContext(requestContext);
        return oapRequest;
    }

    @Override
    @SuppressWarnings("rawtypes")
    public void registerConverters(ApplicationContext context) {

        Map<String, OapConverter> converterMap = context.getBeansOfType(OapConverter.class);

        log.info("注册类型转换器");
        if (converterMap.size() > 0) {
            for (OapConverter converter : converterMap.values()) {
                log.info("注册类型转换器:{}", converter.getClass().getName());
                this.formattingConversionService.addConverter(converter);
            }
        }
        log.info("共注册了{}个类型转换器", converterMap.size());
    }

    /**
     * /**
     * 获取默认的格式化转换器
     *
     * @return 格式化转换器
     */
    private FormattingConversionService getDefaultConversionService() {
        FormattingConversionServiceFactoryBean serviceFactoryBean = new FormattingConversionServiceFactoryBean();
        serviceFactoryBean.afterPropertiesSet();
        return serviceFactoryBean.getObject();
    }

    private String getClientIp(HttpServletRequest request) {
        String clientIp = request.getRemoteAddr();
        for (String headerName : REMOTE_IP_ADDRESS) {
            String ip = request.getHeader(headerName);
            if (StringUtils.isNotBlank(ip) && !"null".equalsIgnoreCase(ip)) {
                clientIp = ip;
                break;
            }
        }
        return clientIp;
    }

    private AbstractOapRequest buildRequestFromBindingResult(OapRequestContext requestContext, BindingResult bindingResult) {
        if (bindingResult.getTarget() instanceof AbstractOapRequest) {
            AbstractOapRequest oapRequest = (AbstractOapRequest) bindingResult.getTarget();
            oapRequest.setRequestContext(requestContext);
            return oapRequest;
        } else {
            if (bindingResult.getTarget() != null) {
                log.warn(bindingResult.getTarget().getClass().getName() + "不是扩展于" + AbstractOapRequest.class.getName() + ",无法设置" + OapRequestContext.class.getName());
            }
            return null;
        }
    }

    private BindingResult doBind(Map<String, Object> requestParams, Class<? extends OapRequest> requestType) {
        OapRequest bindObject = BeanUtils.instantiateClass(requestType);
        BeanWrapper beanWrapper = new BeanWrapperImpl(bindObject);
        for (String key : requestParams.keySet()) {
            if (beanWrapper.isWritableProperty(key)) {
                beanWrapper.setPropertyValue(key, requestParams.get(key));
            }
        }
        ServletRequestDataBinder dataBinder = new ServletRequestDataBinder(bindObject, "bindObject");
        dataBinder.setConversionService(this.formattingConversionService);
        dataBinder.setValidator(getValidator());

//		dataBinder.bind(servletRequest);
        dataBinder.validate();
        return dataBinder.getBindingResult();
    }

    public Validator getValidator() {
        if (this.validator == null) {
            LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
            localValidatorFactoryBean.afterPropertiesSet();
            this.validator = localValidatorFactoryBean;
        }
        return this.validator;
    }
}
